package com.basker.pisces.orm.session;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.util.CollectionUtils;

import com.basker.pisces.core.IInitialize;
import com.basker.pisces.core.iterate.DataObjectIterateEvent;
import com.basker.pisces.core.iterate.DataObjectIterator;
import com.basker.pisces.core.iterate.IDataObjectIterateHandler;
import com.basker.pisces.core.meta.IDataField;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.meta.IReferenceField;

class ReferenceFieldLoader {
    private Map<String, RefDataObjects> refrenceDataObjects = new HashMap<String, RefDataObjects>();
    private IDataObjectMeta objectMeta;
    private ISqlSession sqlSession;

    public ReferenceFieldLoader(ISqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }

    public void loadRefrenceFields(IDataObjectMeta objectMeta, Object[] dataObjects) {
        this.objectMeta = objectMeta;
        analysislRefDataObjects(dataObjects);
        mapRefrenceObjects();
    }

    protected void addToRefObjects(DataObjectIterateEvent event) {
        Collection<IReferenceField> refrenceFields = event.getObjectMeta().getReferenceFields();
        if (CollectionUtils.isEmpty(refrenceFields)) {
            return;
        }

        for (IReferenceField refField : refrenceFields) {
            IDataObjectMeta refObjectMeta = refField.getDataObjectMeta();
            if (refObjectMeta == null) {
                continue;
            }

            // 为每个关联的契约建立一个关联对象组
            RefDataObjects refrenceDataObjectList = refrenceDataObjects.get(refObjectMeta.getName());
            if (refrenceDataObjectList == null) {
                refrenceDataObjectList = new RefDataObjects(refObjectMeta);
                refrenceDataObjects.put(refObjectMeta.getName(), refrenceDataObjectList);
            }

            for (Object dataObject : event.getDataObjects()) {
                Object pkId = refField.getReferenceIdField().getValue(dataObject);
                if (pkId != null) {
                    RefDataObject refObj = refrenceDataObjectList.get(pkId.toString());
                    if (refObj == null) {
                        // 创建关联对象 记录主键 放入对象组中
                        refObj = new RefDataObject(pkId.toString());
                        refrenceDataObjectList.put(refObj);
                    }
                    // 记录关联对象的位置：哪个对象的哪个字段
                    // 一个关联对象可能有多个位置
                    refObj.addRefFieldLocation(new RefFieldLocation(refField, dataObject));
                }
            }
        }

    }

    private void analysislRefDataObjects(Object[] dataObjects) {
        DataObjectIterator iterator = new DataObjectIterator(dataObjects, this.objectMeta,
                new IDataObjectIterateHandler() {
                    @Override
                    public void handleDataObjects(DataObjectIterateEvent event) {
                        addToRefObjects(event);
                    }
                });
        iterator.iterate();
    }

    private Object[] load(IDataObjectMeta refObjectMeta, List<Object> oids) {
        Object[] objects = this.sqlSession.loadBatch(refObjectMeta, oids.toArray(), true, false);
        ReferenceFieldLoader loader = new ReferenceFieldLoader(this.sqlSession);
        loader.loadRefrenceFields(refObjectMeta, objects);
        return objects;
    }

    private void mapRefrenceField(RefDataObjects refrenceDataObjectList, Object[] refDataObjs) {
        if (refDataObjs != null) {
            IDataField pkField = refrenceDataObjectList.getDataObjectMeta().getPkField();
            for (int i = 0; i < refDataObjs.length; i++) {
                Object refDataObject = refDataObjs[i];
                Object oid = pkField.getValue(refDataObject);
                RefDataObject ref = refrenceDataObjectList.get(oid.toString());
                if (ref != null) {
                    ref.setDataObject(refDataObject);
                }
            }
        }

        for (RefDataObject ref : refrenceDataObjectList.getRefDataObjects()) {
            if (ref.getDataObject() == null) {
                ref.setDataObject(null);
            }
        }

    }

    private void mapRefrenceObjects() {
        if (refrenceDataObjects.size() > 0) {
            for (RefDataObjects refrenceDataObjectList : refrenceDataObjects.values()) {
                List<Object> ids = refrenceDataObjectList.getNeedLoadIds();
                if (ids.size() > 0) {
                    Object[] refDataObjs = this.load(refrenceDataObjectList.getDataObjectMeta(), ids);
                    mapRefrenceField(refrenceDataObjectList, refDataObjs);
                }
            }
        }

    }

    class RefDataObject {
        private Object id;
        private Object dataObject;
        private boolean isLoaded = false;
        private List<RefFieldLocation> locations = new ArrayList<RefFieldLocation>();

        public RefDataObject(Object id) {
            this.id = id;
        }

        public void addRefFieldLocation(RefFieldLocation location) {
            locations.add(location);
        }

        public Object getDataObject() {
            return dataObject;
        }

        public void setDataObject(Object value) {
            dataObject = value;
            isLoaded = true;

            for (RefFieldLocation location : locations) {
                Object curObject = location.dataObject;

                try {
                    if (curObject instanceof IInitialize) {
                        ((IInitialize) curObject).beginInit();
                    }

                    location.getRefField().setValue(curObject, dataObject);
                    if (dataObject == null) {
                        location.getRefField().getReferenceIdField().setValue(curObject, null);
                    }
                } finally {
                    if (curObject instanceof IInitialize) {
                        ((IInitialize) curObject).endInit();
                    }
                }
            }
        }
    }

    class RefDataObjects {
        private IDataObjectMeta objectMeta;

        private Map<Object, RefDataObject> dataObjectIdMap = new HashMap<Object, RefDataObject>();

        public RefDataObjects(IDataObjectMeta objectMeta) {
            this.objectMeta = objectMeta;
        }

        public RefDataObject get(Object pkId) {
            return dataObjectIdMap.get(pkId);
        }

        public IDataObjectMeta getDataObjectMeta() {
            return objectMeta;
        }

        public Collection<Object> getIds() {
            return dataObjectIdMap.keySet();
        }

        public List<Object> getNeedLoadIds() {
            List<Object> cols = new ArrayList<Object>(dataObjectIdMap.size());
            for (RefDataObject refObj : dataObjectIdMap.values()) {
                if (!refObj.isLoaded) {
                    cols.add(refObj.id);
                }
            }
            return cols;
        }

        public Collection<RefDataObject> getRefDataObjects() {
            return dataObjectIdMap.values();
        }

        public void put(RefDataObject refObj) {
            dataObjectIdMap.put(refObj.id, refObj);
        }

    }

    class RefFieldLocation {
        private IReferenceField refField;
        private Object dataObject;

        public RefFieldLocation(IReferenceField refField, Object dataObject) {
            this.refField = refField;
            this.dataObject = dataObject;
        }

        public Object getDataObject() {
            return dataObject;
        }

        public IReferenceField getRefField() {
            return refField;
        }

    }
}
